#!/bin/bash
#
# Control scheduler implementation: uses timestamp data to simulate scheduling
# for the purpose of visualizing execution traces
#
# Author:   Mike Murphy <mamurph@cs.clemson.edu>
# Revision: 23 September 2009
#
#   Copyright 2009 Clemson University
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.
#
# This material is based upon work supported under a National Science
# Foundation Graduate Research Fellowship. Any opinions, findings,
# conclusions or recommendations expressed in this publication are those of
# the author(s) and do not necessarily reflect the views of the National
# Science Foundation.

# Experiment control scheduler: hacked to produce timing behavior exactly as
# observed on EGEE (from the trace data). Any discrepancies (e.g. data set
# shows a job being run when there are no free cores) will be reported to a
# DiscrepancyReport object.

'''
Control Scheduler: uses job queue waiting time from the input data to
simulate job execution. No actual scheduling discipline is used, since the
purpose of this component is to assist visualizations of the input data.

Jobs that do not have queuing time information will be discarded.
'''

from cluster_sim import Machine
from results import DiscardResult, DiscrepancyResult, JobResult
from scheduler import Scheduler

phantom_index = 1


class ControlScheduler(Scheduler):
   '''
   Scheduler replacement that uses observed queue delays to replicate the
   behavior of the actual grid. If queue delay information is missing, then
   the job will be discarded. Discrepancies may occur when the queuing delay
   is negative or when the job shows as running, but there are no available
   scheduler slots on which to begin execution.
   '''
   def __init__(self, simobj, tag='', **kwds):
      '''
      @param simobj: Reference to the simulator object
      @param tag: Optional scheduler tag
      @type tag: str
      @param kwds: Keyword arguments for scheduler extra_data
      '''
      Scheduler.__init__(self, simobj, name='_control_', tag='', **kwds)
   #
   # NOTE: machine is ignored in start_job (required argument due to parent
   # class signature)
   def start_job(self, job, machine=None):
      '''
      Starts a job on a free CPU core (Machine). If no free core is available,
      a "phantom" core, not associated with any cluster or site, is created
      to run the job. Phantom core creations are reported as
      DiscrepancyResults, since their presence indicates a mismatch between
      the job trace data and the grid map data.

      @param job: Job to be started
      @param machine: Unused, but required due to parent class signature

      @return: results collection for simulator output
      '''
      node = self.pick_slot(job)
      r = []
      if not node:
         #
	 # Create a phantom worker node
	 global phantom_index
	 m_name = 'PHANTOM_%d' % phantom_index
	 m = Machine(m_name)
	 phantom_index += 1
	 self.add_target(m)
	 #
	 # Claim new machine
	 node = self.pick_slot(job)
	 assert node is not None    # If the 2nd claim failed, we have a bug
         #
	 # Report the issue
	 job_id = job.name
         reason = 'job started when no free slots in scheduler'
	 resolution = 'adding a phantom node to the scheduler pool'
	 ts = self.sim.get_time()
	 extra = {'new_machine_name': m_name}
         r.append(DiscrepancyResult(ts, job_id, reason, resolution, extra))
      #
      self.claim_slot(node)
      r.append(Scheduler.start_job(self, job, node))
      r.append(self.get_update())
      return (r,)
   def schedule(self, time, simobj):
      '''
      Does nothing in this implementation, but returns a scheduler update
      result collection.

      @return: simulator results collection
      '''
      # Disable parent class scheduling
      return self.get_update()
   def submit_job(self, job):
      '''
      Handles job submission to the control scheduler. If the job has queue
      delay information (a queue_time field), then the delay will be checked
      for sanity. If negative, a DiscrepancyResult will be added to the
      collection, the job length will be set to zero, and the job will be
      marked with an error condition.

      If the queue delay information is missing for a job, then the job will
      be discarded (producing a DiscardResult).

      @param job: Job to be submitted

      @return: results collection for simulator output
      '''
      r = []
      cur_time = self.sim.get_time()
      if ('queue_time' in job.extra_data):
         r.append(self.enqueue_job(job))
         qt = job.extra_data['queue_time']
         if qt < 0:
            job_id = job.name
	    reason = 'negative queue delay time ' + str(qt)
	    resln = 'flagging job as error (length will be zero)'
	    r.append(DiscrepancyResult(cur_time, job_id, reason, resln, \
                                      job.extra_data))
            job.error = True
            job.length = 0
            qt = 0
         #
         st = cur_time + qt
         self.sim.new_external_event(st, self.start_job, job, None)
         r.append(self.get_update())
      else:
         job.error = True
         r.append(DiscardResult(cur_time, 'missing queue_time field', job))
      return (r,)
#


def new_control_scheduler(ignored, site_name, simobj, *args, **kwds):
   '''
   Creates and returns a control scheduler

   @param ignored: Ignored (present for interface compatibility)
   @param site_name: Site name where the scheduler will be located
   @type site_name: str
   @param simobj: Reference to the simulator instance
   @param args: Arguments to be passed to the simulator constructor
   @param kwds: Keywords to be passed to the simulator constructor

   @return: a ControlScheduler instance
   '''
   sched = ControlScheduler(simobj, tag=site_name, *args, **kwds)
   return sched
#
